/* Copyright (c) 2007 Nordic Semiconductor. All Rights Reserved.
 *
 * The information contained herein is property of Nordic Semiconductor ASA.
 * Terms and conditions of usage are described in detail in NORDIC
 * SEMICONDUCTOR STANDARD SOFTWARE LICENSE AGREEMENT. 
 *
 * Licensees are granted free, non-transferable use of the information. NO
 * WARRENTY of ANY KIND is provided. This heading must NOT be removed from
 * the file.
 *
 * $LastChangedRevision: 2290 $
 */ 

/** @file
 *
 * Host source file for the Wireless Desktop Protocol. 
 *
 * @author Lasse Olsen
 *
 */
 
#include <stdint.h>
#include <string.h>
#include "wdp_host.h"

#include "app_param.h"
/**
Device parameter struct type definition. 
*/ 
typedef struct
{
  uint16_t dev_keep_alive_interval;   
  uint8_t dev_b0_address;            
} wdp_dev_params_t;

const wdp_dev_params_t wdp_dev_params[WDP_DEVICE_SUPPORT_SIZE]=
{
  {WDP_MOUSE_KA_INTERVAL, WDP_MOUSE_ADR_B0},
  {WDP_KEYBOARD_KA_INTERVAL, WDP_KEYBOARD_ADR_B0},
  {WDP_REMOTE_KA_INTERVAL, WDP_REMOTE_ADR_B0} 
};

typedef struct 
{
  uint8_t pairing[FAP_ADDRESS_WIDTH];
  uint8_t master[FAP_ADDRESS_WIDTH-1];
} wdp_addresses_t;  

static wdp_addresses_t wdp_addresses =           
{
  WDP_PAIRING_ADDRESS,
  WDP_INIT_MASTER_ADDRESS
};

/** 
WDP status register flag.
*/
#define WDP_EN_ADR_GEN 0

/** 
WDP status register flag.
*/
#define WDP_PAIRING_EXECUTED 1
/** 
WDP status register flag.
*/
#define WDP_APP_DATA_RDY 2

//***************************************************************************** 
// Misc. macros
//*****************************************************************************

#define WDP_GET_BIT(a, b) ((a >> b) & 0x01)
#define WDP_CLEAR_BIT(a, b) (a &= ~(1 << b))
#define WDP_SET_BIT(a, b) (a |= (1 << b)) 
                         
static xdata uint8_t wdp_status_reg;

static xdata fap_tx_rx_struct_t wdp_rx_struct;
static xdata fap_tx_rx_struct_t wdp_gen_purp_struct;

static xdata uint16_t wdp_device_alive_array[WDP_DEVICE_SUPPORT_SIZE];
static const uint8_t wdp_pairing_ch_tab[FAP_CH_TAB_SIZE] = WDP_PAIRING_CHANNELS;

bool wdp_host_get_rx_data(uint8_t *dst, uint8_t* length, wdp_dev_types_t* origin)
{
  if(WDP_GET_BIT(wdp_status_reg, WDP_APP_DATA_RDY))
  {
    *length = (wdp_rx_struct.pl_length - WDP_APP_PL_START); 
    *origin = (wdp_dev_types_t) wdp_rx_struct.pipe; 
    memcpy(dst, &wdp_rx_struct.pl[WDP_APP_PL_START], *length);
    WDP_CLEAR_BIT(wdp_status_reg, WDP_APP_DATA_RDY);
    return true;
  }
  else
  {
    return false;
  }
}

void wdp_host_init(void)
{
  uint8_t temp_adr;
  fap_init();
  fap_select_radio_idle_mode(FAP_PDOWN_IDLE);       

  fap_set_address(FAP_DEVICE0, (uint8_t*) &wdp_addresses.pairing[0]);
  
  temp_adr = WDP_KEYBOARD_ADR_B0;
  fap_set_address(FAP_DEVICE2, &temp_adr);

  temp_adr = WDP_REMOTE_ADR_B0;
  fap_set_address(FAP_DEVICE3, &temp_adr);
   
  WDP_SET_BIT(wdp_status_reg, WDP_EN_ADR_GEN);
  WDP_CLEAR_BIT(wdp_status_reg, WDP_PAIRING_EXECUTED);
}
 
void wdp_host_get_master_adr(uint8_t *adr)
{
  memcpy(adr, &wdp_addresses.master[0], (FAP_ADDRESS_WIDTH-1));
}

void wdp_host_enable_random_adr_gen(void)
{
  WDP_SET_BIT(wdp_status_reg, WDP_EN_ADR_GEN);
}

bool wdp_host_set_master_adr(uint8_t *adr)
{
  if(fap_get_mode() == FAP_IDLE)
  {
    memcpy(&wdp_addresses.master[0], adr, (FAP_ADDRESS_WIDTH-1));   
    
    wdp_gen_purp_struct.pl[0] = wdp_dev_params[0].dev_b0_address;
    memcpy(&wdp_gen_purp_struct.pl[1], adr, (FAP_ADDRESS_WIDTH-1));
    
    WDP_CLEAR_BIT(wdp_status_reg, WDP_EN_ADR_GEN);
  
    fap_set_address(FAP_DEVICE1, &wdp_gen_purp_struct.pl[0]);
  
    return true;
  }
  else
  {
    return false;
  } 
}

void wdp_host_rx_setup(wdp_rx_setup_t setup)
{

  fap_goto_idle_mode();

  fap_flush_rx_fifo();
  fap_flush_tx_fifo();

  if(setup == WDP_RX_PAIRING)
  {
    WDP_CLEAR_BIT(wdp_status_reg, WDP_PAIRING_EXECUTED);  
    
    // Setup pairing receive channels
    fap_set_channels(wdp_pairing_ch_tab);
    
    // Note: Pipe 0 (pairing) address setup by wdp_host_init()
    fap_rx_data( (uint8_t) setup, 0);
  }
  else
  if(setup == WDP_RX_NORMAL)
  {
    // Setup receive channels
    wdp_extract_channels(wdp_addresses.master[0], &wdp_gen_purp_struct.pl[0]); 
    fap_set_channels(&wdp_gen_purp_struct.pl[0]);

    // Setup pipe 1 address (master)
    wdp_gen_purp_struct.pl[0] = wdp_dev_params[0].dev_b0_address; // Mouse address on pipe 1                
    memcpy(&wdp_gen_purp_struct.pl[1], &wdp_addresses.master[0], FAP_ADDRESS_WIDTH -1);        
    fap_set_address(FAP_DEVICE1, &wdp_gen_purp_struct.pl[0]);  
    
    fap_rx_data( (uint8_t) setup, 0);
  }
  else
  if(setup == WDP_RX_SUSPEND)
  {
    fap_rx_data( (uint8_t) setup, FAP_RX_SINGLE_CH_REV); 
  }
}

bool wdp_host_get_clear_pairing_result(void)
{
  bool retbit; 
  retbit = WDP_GET_BIT(wdp_status_reg, WDP_PAIRING_EXECUTED);
  WDP_CLEAR_BIT(wdp_status_reg, WDP_PAIRING_EXECUTED);
  return retbit; 
}

bool wdp_host_process_events(void)
{

#if USE_WDP
  uint8_t temp_dev_index;

  // Random address counter    
  if(WDP_GET_BIT(wdp_status_reg, WDP_EN_ADR_GEN))
  {
    (*((uint16_t*)(&wdp_addresses.master[2]))) += 4;  
    (*((uint16_t*)(&wdp_addresses.master[0])))++;  
  }
  
  // Check if received data
  if(fap_read_rx_fifo(&wdp_rx_struct))
  {
    WDP_CLEAR_BIT(wdp_status_reg, WDP_APP_DATA_RDY);

    if(wdp_rx_struct.pipe == WDP_PAIRING_PIPE)
    {
      switch(wdp_rx_struct.pl[WDP_TYPE_ID])
      {
        case PAIRING_REQ:
          temp_dev_index = wdp_rx_struct.pl[DEV_TYPE] - 1;
        
          if(!(temp_dev_index < WDP_DEVICE_SUPPORT_SIZE))
          {
            break;
          }        

          WDP_CLEAR_BIT(wdp_status_reg, WDP_EN_ADR_GEN); // Stop address generator
        
          // Assemble pairing return payload 
          wdp_gen_purp_struct.pl[WDP_TYPE_ID] = PAIRING_RESP;
          wdp_gen_purp_struct.pl[DEV_TYPE] = wdp_rx_struct.pl[DEV_TYPE];
          
          wdp_gen_purp_struct.pl[ADDRESS_B0] = wdp_dev_params[temp_dev_index].dev_b0_address;                  

          memcpy(&wdp_gen_purp_struct.pl[MASTER_ADDRESS_START], wdp_addresses.master, FAP_ADDRESS_WIDTH -1 );  
          wdp_gen_purp_struct.pipe = WDP_PAIRING_PIPE;
          wdp_gen_purp_struct.pl_length = WDP_PAIRING_PL_LENGTH;

          // Write pairing return payload to downlink buffer (ack payload) 
          
          fap_write_ack_pload(&wdp_gen_purp_struct);  
                                                     
          break;
        case PAIRING_COMPL:
          WDP_SET_BIT(wdp_status_reg, WDP_PAIRING_EXECUTED);
          break;
        default:
          break;
      }    
    }
    else
    {
      temp_dev_index = wdp_rx_struct.pipe - 1;
    
      if(temp_dev_index < WDP_DEVICE_SUPPORT_SIZE) // If data received on device address (not pairing address)
      {
        wdp_device_alive_array[temp_dev_index]= wdp_dev_params[temp_dev_index].dev_keep_alive_interval*2;
      }
      
      switch(wdp_rx_struct.pl[WDP_TYPE_ID])
      {
        case KEEP_ALIVE:
          break; 
        case USER_DATA:
          WDP_SET_BIT(wdp_status_reg, WDP_APP_DATA_RDY);
          break;
        default:
          break;
      }
    }   
    return true;
  }

  return false;
#else 
  return false;

#endif	//end if  USE_WDP
}

void wdp_timer_isr_function(void)
{
  uint8_t index; 
  for(index=0; index<WDP_DEVICE_SUPPORT_SIZE; index++)
  {
    if(wdp_device_alive_array[index]>1)               // Stops at 1 when going from connect to disconnect
    {
      wdp_device_alive_array[index]--;
    }
  }
  return; 
}

bool wdp_host_write_downlink_data(wdp_dev_types_t dev_type, uint8_t *src, uint8_t length)
{
  if(!(length > WDP_MAX_DL_PL_LENGTH))
  {
    memcpy(&wdp_gen_purp_struct.pl[0], src, length);  
    wdp_gen_purp_struct.pipe = dev_type;
    wdp_gen_purp_struct.pl_length = length;
      
    return fap_write_ack_pload(&wdp_gen_purp_struct);
  }
  else
  {
    return false;
  }
}

bool wdp_host_get_connection_status(wdp_dev_types_t dev_type)
{
  if(wdp_device_alive_array[dev_type-1] == 1)
  {
    wdp_device_alive_array[dev_type-1] = 0;
    return false;  
  }
  else
  {
    return true;
  }
}